#include "StdAfx.h"
#include "Particles.h"
#include "mmsystem.h"

using namespace std;

inline double random(double fm, double to)
{
    return fm + (to - fm) * double(rand()) / double(RAND_MAX);
}



CParticles::CParticles(void)
{
	    m_partimage = NULL;
    m_gravity = false;
    m_bounce = 0;           // None
    m_emission = false;
    m_decay = false;
    m_energycolor = false;

	srand( (unsigned)time( NULL ) );
    m_firstdraw = true;
    m_run = false;
    timeBeginPeriod(1);
}

CParticles::~CParticles(void)
{
	    if(m_partimage)
    {
        delete m_partimage[0];
        delete m_partimage;
    }
}

void CParticles::Clear()
{
    m_particles.clear();
    m_gravity = false;
}
void CParticles::LoadParticle(const CGrVector &location, const CGrVector &velocity, const float *color, double energy)

{
    Particle p;
    p.p = location;
    p.v = velocity;
    p.color[0] = color[0];
    p.color[1] = color[1];
    p.color[2] = color[2];
    p.energy = energy;

    m_particles.push_back(p);
}


void CParticles::Update(double dt)
{
	if(m_emission)
    {
        for(int i=0;  i<(rand() % 5000);  i++)
        {
            CGrVector p(0, 1, 0);
            CGrVector v(random(-3, 3), random(1, 100), random(-3, 3));
            float color[] = {1, 1, 1};
            LoadParticle(p, v, color, random(0.8, 1));
        }
    }

    for(std::list<Particle>::iterator p=m_particles.begin();  p!=m_particles.end();  )
    {
        std::list<Particle>::iterator next = p;
        next++;

        if(m_decay)
        {
            p->energy *= pow(0.3, dt);
            if(p->energy < 0.001)
            {
                m_particles.erase(p);
                p = next;
                continue;
            }
        }

        // This is how much we need to accomplish
        double deltaToDo = dt;

        // Keep trying until we get it done
        while(deltaToDo > 0)
        {
            double deltaTry = deltaToDo;

           while(true)
		   {
                if(TryUpdate(*p, deltaTry))
                {
                    deltaToDo -= deltaTry;
                    break;
                }

                deltaTry /= 2;      // Try half as much
            }

        }

        p = next;
    }

}
bool CParticles::TryUpdate(Particle &particle, double dt)
{
    CGrVector a(0, 0, 0, 0);     // Acceleration

    if(m_gravity && (particle.p.Y() > 0 || particle.v.Y() > 0))
    {
        a.Y(-9.80665);        // m/sec
    }

    // Euler steps
    CGrVector v = particle.v + a * dt;
    CGrVector p = particle.p + v * dt;


    switch(m_bounce)
    {
    case 0:
        break;

    case 1:     // First approximation
        if(p.Y() < 0)
        {
            CGrVector N(0, 1, 0, 0);     // Straight up
            v = N * -2 * Dot3(N, v) + v;
        }

        break;

    case 2:     // Better approximation
        if(p.Y() < 0 && v.Y() < 0)
        {
            CGrVector N(0, 1, 0, 0);     // Straight up
            v = N * -2 * Dot3(N, v) + v;
        }

        break;

    case 3:
        // Are we too far below?
        if(particle.p.Y() > 0 && p.Y() < -0.0000000001)
            return false;

        if(p.Y() < 0 && v.Y() < 0)
        {
            CGrVector N(0, 1, 0, 0);     // Straight up
            v = N * -2 * Dot3(N, v) + v;
            p.Y(0);
        }

        break;

    case 4:     // With loss
        // Are we too far below?
        if(particle.p.Y() > 0 && p.Y() < -0.00000001)
            return false;

        if(p.Y() < 0 && v.Y() < 0)
        {
            CGrVector N(0, 1, 0, 0);     // Straight up
            v = N * -2 * Dot3(N, v) + v;
            v = v * 0.75;
            p.Y(0);
        }


        break;

    case 5:     // With random bounce
        // Are we too far below?
        if(particle.p.Y() > 0 && p.Y() < -0.00000001)
            return false;

        if(p.Y() < 0 && v.Y() < 0)
        {
            CGrVector N(0, 1, 0, 0);     // Straight up
            v = N * -2 * Dot3(N, v) + v;
            v = v * random(0.50, 0.80);
            p.Y(0);
        }

        break;

    case 6:     // With random bounce
        // Are we too far below?
        if(particle.p.Y() > 0 && p.Y() < -0.00000001)
            return false;

        if(p.Y() < 0 && v.Y() < 0)
        {
            CGrVector N(0, 1, 0, 0);     // Straight up
            v = N * -2 * Dot3(N, v) + v;
            v = v * random(0.50, 0.80);
            v.X(v.X() + random(-3, 3));
            v.Z(v.Z() + random(-3, 3));
            p.Y(0);
        }

        break;

    }


    particle.v = v;
    p.W(1);
    particle.p = p;

    return true;
    
}
void CParticles::Render(int wid, int hit)
{
    //
    // Now we need a memory buffer, just like we did for OpenGL.  However, I'm making it 
    // floats instead of bytes, because we'll need the precision.  Because floats are four
    // bytes the alignment will come for free.  Note that you won't need to worry about
    // any range bounding here, OpenGL will do it for you.
    //

    if(m_partimage == NULL || m_partimagewidth != wid || m_partimageheight != hit)
    {
        m_partimagewidth = wid;
        m_partimageheight = hit;
        
        if(m_partimage)
        {
            delete m_partimage[0];
            delete m_partimage;
            m_partimage = NULL;
        }

        m_partimage = new float *[m_partimageheight];
        m_partimage[0] = new float[m_partimageheight * m_partimagewidth * 3];

        for(int i=1; i<m_partimageheight; i++)
        {
            m_partimage[i] = m_partimage[0] + i * m_partimagewidth * 3;
        }

    }

    for(int i=0; i<m_partimageheight; i++)
    {
        // Fill the image with black
        for(int j=0; j<m_partimagewidth; j++)
        {
            m_partimage[i][j * 3] = 0;              // red
            m_partimage[i][j * 3 + 1] = 0;          // green
            m_partimage[i][j * 3 + 2] = 0;          // blue
        }
    }

    // Okay, now OpenGL is all configured as if we were drawing.  
    // Obtain the matrices from OpenGL

    GLdouble m[16];
    GLdouble *pm;

    // Obtain the ModelView matrix and convert it to CGrTransform
    glGetDoublev(GL_MODELVIEW_MATRIX, m);
    CGrTransform modelViewMatrix;
    pm = m;
    for(int c=0;  c<4;  c++)
        for(int r=0;  r<4;  r++)
            modelViewMatrix[r][c] = *pm++;

    // Obtain the projection matrix and convert it to CGrTransform
    glGetDoublev(GL_PROJECTION_MATRIX, m);
    CGrTransform projectionMatrix;
    pm = m;
    for(int c=0;  c<4;  c++)
        for(int r=0;  r<4;  r++)
            projectionMatrix[r][c] = *pm++;

    // We can go ahead and multiply these together
    m_partxform = projectionMatrix * modelViewMatrix;

    // Obtain the glViewport parameters
    glGetIntegerv(GL_VIEWPORT, m_vp);

    // Project all of my particles
    for(std::list<Particle>::iterator p=m_particles.begin();  p!=m_particles.end();  p++)
    {
        p->projected = m_partxform * p->p;
    }

    m_particles.sort();

    // Render all of the particles
    for(std::list<Particle>::iterator p=m_particles.begin();  p!=m_particles.end();  p++)
    {
        ParticleRender(*p);
    }
}

void CParticles::ParticleRender(const Particle &p)
{
    // Test for content behind us
    if(p.projected[2] <= 0)
        return;         // Don't draw

    // Homogenize
    double x = p.projected[0] / p.projected[3];
    double y = p.projected[1] / p.projected[3];

    // Viewport conversion
    int sx = int((x + 1) * m_vp[2] / 2 + m_vp[0]);
    int sy = int((y + 1) * m_vp[3] / 2 + m_vp[1]);

    // Clip anything that is off the screen
    if(sx < 0 || sx >= m_partimagewidth || sy < 0 || sy >= m_partimageheight)
        return;

    if(m_energycolor)
    {
        m_partimage[sy][sx * 3] = (float)p.energy * 1.5f;
        m_partimage[sy][sx * 3 + 1] =  1 - ((float)abs(p.energy - 0.3)) * 2.0f;
        m_partimage[sy][sx * 3 + 2] = 1 - (float) p.energy * 1.5f;
    }
    else
    {
        // And add it to the pixel
        float translucency = 0.f;
        m_partimage[sy][sx * 3] = translucency * m_partimage[sy][sx * 3] + p.color[0];
        m_partimage[sy][sx * 3 + 1] = translucency * m_partimage[sy][sx * 3 + 1] + p.color[1];
        m_partimage[sy][sx * 3 + 2] = translucency * m_partimage[sy][sx * 3 + 2] + p.color[2];
    }
	
}

void CParticles::Display(int width, int height)
{
    //
    // Set up camera to just display the image
    //

	glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, width, 0, height, -1, 1);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glRasterPos3i(0, 0, 0);
	glDrawPixels(m_partimagewidth, m_partimageheight, 
        GL_RGB, GL_FLOAT, m_partimage[0]);
	glDepthMask(1);

}

void CParticles::Render()
{

 

}